#include <pthread.h>
#include <stdio.h>
#include <malloc.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/prctl.h>
#include <arpa/inet.h>
#include "process.h"

int counter = 0;

extern void *sock_accept(void *arg);
extern int thread_add_socket_task(thread_pool_t *thread_pool, sock_info_t *sock_info);
void *func0(void *arg)
{
    int num = *((int *)arg);
    printf("%s %d: working, arg is %d\n", __func__, __LINE__, num);
    return NULL;
}

void *func1(void *arg)
{
    int num = *((int *)arg);
    printf("%s %d: working, arg is %d\n", __func__, __LINE__, num);
    return NULL;
}

void *func2(void *arg)
{
    int num = *((int *)arg);
    printf("%s %d: working, arg is %d\n", __func__, __LINE__, num);
    return NULL;
}

void *func3(void *arg)
{
    int num = *((int *)arg);
    printf("%s %d: working, arg is %d\n", __func__, __LINE__, num);
    return NULL;
}

worker_thread_t *find_thread_in_work_thread_list(worker_thread_t *work_thread, int list_size, pthread_t tid)
{
    int i = 0;
    for (i = 0; i < list_size; i++)
    {
        if (pthread_equal(work_thread[i].tid, tid))
        {
            return &work_thread[i];
        }
    }
    return NULL;
}

void *thread_worker(void *arg)
{
    printf("%s %d: come here\n", __func__, __LINE__);
    thread_pool_t *thread_pool = NULL;
    worker_thread_t *thread_self = NULL;
    thread_task_t thread_task;
    if (arg == NULL)
    {
        printf("%s %d: thread pool is null\n", __func__, __LINE__);
        return NULL;
    }
    thread_pool = (thread_pool_t *)arg;
    while (1)
    {
        pthread_mutex_lock(&thread_pool->thread_pool_mutex);
        /*If thread pool shutdown, thread must exit.*/
        if (thread_pool->shutdown == 1)
        {
            printf("%s %d: Thread pool shutdown, thread %ld exits.\n",
                   __func__, __LINE__, pthread_self());
            thread_pool->work_thread_live--;
            pthread_mutex_unlock(&thread_pool->thread_pool_mutex);
            pthread_exit(NULL);
        }

        if (thread_pool->work_thread_exit)
        {
            thread_pool->work_thread_exit--;
            /*
             * If live thread num is less than min_thread_num,
             * just decrease work_thread_exit, do not exit thread.
             */
            if (thread_pool->work_thread_live > thread_pool->work_thread_min)
            {
                thread_self = find_thread_in_work_thread_list(thread_pool->worker_thread, thread_pool->work_thread_max, pthread_self());
                if (thread_self)
                {
                    printf("%s %d: Find thread %lu\n", __func__, __LINE__, pthread_self());
                    thread_self->is_use = 0;
                }
                else
                {
                    printf("%s %d: Something error, cannot find pthread %lu in thread list\n",
                           __func__, __LINE__, pthread_self());
                }
                thread_pool->work_thread_live--;
                printf("%s %d: Thread %lu exits\n", __func__, __LINE__, pthread_self());
                pthread_mutex_unlock(&thread_pool->thread_pool_mutex);
                pthread_exit(NULL);
            }
            else
            {
                printf("%s %d: min thread num is %d, live thread num is %d,\
                       thread %ld cannot exit.\n",
                       __func__, __LINE__,
                       thread_pool->work_thread_min, thread_pool->work_thread_live, pthread_self());
            }
        }

        if (thread_pool->task_list_cur_size <= 0)
        {
            pthread_cond_wait(&thread_pool->consumer_num, &thread_pool->thread_pool_mutex);
            /*Double check*/
            if (thread_pool->task_list_cur_size <= 0)
            {
                pthread_mutex_unlock(&thread_pool->thread_pool_mutex);
                continue;
            }
        }
        thread_task = thread_pool->task_list[thread_pool->task_list_front];
        thread_pool->task_list[thread_pool->task_list_front].function = NULL;
        thread_pool->task_list[thread_pool->task_list_front].arg = NULL;
        thread_pool->task_list_front = (thread_pool->task_list_front + 1) % thread_pool->task_list_capacity;
        thread_pool->task_list_cur_size--;
        pthread_cond_signal(&thread_pool->producer_num);
        pthread_mutex_unlock(&thread_pool->thread_pool_mutex);

        pthread_mutex_lock(&thread_pool->thread_busy_mutex);
        thread_pool->work_thread_busy++;
        pthread_mutex_unlock(&thread_pool->thread_busy_mutex);

        printf("%s %d: ========== Thread %lu starts working ==========\n",
               __func__, __LINE__, pthread_self());
        /*Excute task*/
        if (thread_task.arg == NULL)
        {
            printf("%s %d: Error, argument is null\n", __func__, __LINE__);
            pthread_mutex_lock(&thread_pool->thread_busy_mutex);
            thread_pool->have_fail_work_num++;
            thread_pool->work_thread_busy--;
            pthread_mutex_unlock(&thread_pool->thread_busy_mutex);

            continue;
        }
        if (thread_task.function == NULL)
        {
            printf("%s %d: Error, function is null\n", __func__, __LINE__);
            pthread_mutex_lock(&thread_pool->thread_busy_mutex);
            thread_pool->have_fail_work_num++;
            thread_pool->work_thread_busy--;
            pthread_mutex_unlock(&thread_pool->thread_busy_mutex);
            continue;
        }
        thread_task.function(thread_task.arg);

        pthread_mutex_lock(&thread_pool->thread_busy_mutex);
        thread_pool->have_finish_work_num++;
        pthread_mutex_unlock(&thread_pool->thread_busy_mutex);

        free(thread_task.arg);

        printf("%s %d: ========== Thread %ld ends working ============\n\n",
               __func__, __LINE__, pthread_self());

        pthread_mutex_lock(&thread_pool->thread_busy_mutex);
        thread_pool->work_thread_busy--;
        pthread_mutex_unlock(&thread_pool->thread_busy_mutex);
    }
    return NULL;
}

int work_thread_add(thread_pool_t *thread_pool, int add_num)
{

    printf("%s %d: Come here\n", __func__, __LINE__);
    if (thread_pool == NULL)
    {
        printf("%s %d: Thread pool is null\n", __func__, __LINE__);
        return FALSE;
    }
    if (add_num <= 0)
    {
        printf("%s %d: Thread add num is not bigger than zero\n", __func__, __LINE__);
        return FALSE;
    }
    pthread_mutex_lock(&thread_pool->thread_pool_mutex);

    if (thread_pool->shutdown == 1)
    {
        pthread_mutex_unlock(&thread_pool->thread_pool_mutex);
        printf("%s %d: Thread pool shutdown, cannot add new thread\n", __func__, __LINE__);
        return FALSE;
    }

    worker_thread_t *work_thread = thread_pool->worker_thread;
    int i = 0;
    for (; i < thread_pool->work_thread_max; i++)
    {
        /*If son thread has exit, manager must recycle its resource.*/
        if (work_thread[i].is_use == 0 && work_thread[i].tid)
        {
            printf("%s %d: >>>>>> Start to recycle thread %lu\n",
                   __func__, __LINE__, work_thread[i].tid);
            pthread_join(work_thread[i].tid, NULL);
            printf("%s %d: Successfully recycle thread %lu <<<<<<\n",
                   __func__, __LINE__, work_thread[i].tid);
            work_thread[i].tid = 0;
        }
        if ((add_num > 0) && (work_thread[i].is_use == 0))
        {
            if (pthread_create(&work_thread[i].tid, NULL, thread_worker, (void *)thread_pool))
            {
                printf("%s %d: Fail to create thread\n", __func__, __LINE__);
                work_thread[i].tid = 0;
                continue;
            }
            printf("%s %d: Successfully create thread %ld\n", __func__, __LINE__, work_thread[i].tid);
            work_thread[i].is_use = 1;
            thread_pool->work_thread_live++;
            add_num--;
        }
    }
    pthread_mutex_unlock(&thread_pool->thread_pool_mutex);

    if (add_num > 0)
    {
        printf("%s %d: Warning: Thread pool is full, there is no space for new %d thread.\n",
               __func__, __LINE__, add_num);
        return FALSE;
    }
    return TRUE;
}

int thread_task_add(thread_pool_t *thread_pool, int num)
{
    if (num <= 0)
    {
        printf("%s %d: Error task num %d\n", __func__, __LINE__, num);
        return FALSE;
    }
    pthread_mutex_lock(&thread_pool->thread_pool_mutex);
    printf("%s %d: thread pool live thread num %d\n", __func__, __LINE__, thread_pool->work_thread_live);
    if (thread_pool->shutdown == 1)
    {
        printf("%s %d: Thread pool shutdown, cannot add new task\n", __func__, __LINE__);
        pthread_mutex_unlock(&thread_pool->thread_pool_mutex);
        return FALSE;
    }

    if (thread_pool->task_list_cur_size >= thread_pool->task_list_capacity)
    {
        printf("%s %d: Task list is full, no space for new task\n",
               __func__, __LINE__);
        pthread_cond_wait(&thread_pool->producer_num, &thread_pool->thread_pool_mutex);
        //    pthread_mutex_unlock(&thread_pool->thread_pool_mutex);
    }

    int permit_num = thread_pool->task_list_capacity - thread_pool->task_list_cur_size;
    num = num < permit_num ? num : permit_num;
    /*Check again, ensure num > 0*/
    if (num <= 0)
    {
        printf("%s %d: Task list is full, no space for new task\n",
               __func__, __LINE__);
        pthread_mutex_unlock(&thread_pool->thread_pool_mutex);
        return FALSE;
    }

    int i = 0;
    int task_capacity = thread_pool->task_list_capacity;
    thread_task_t *task_list = thread_pool->task_list;
    pthread_mutex_unlock(&thread_pool->thread_pool_mutex);

    for (; i < num; i++)
    {
        work_func work_function;
        srand(time(NULL));
        long int randomNumber = random();
        switch (randomNumber % 4)
        {
        case 0:
            work_function = func0;
            break;
        case 1:
            work_function = func1;
            break;
        case 2:
            work_function = func2;
            break;
        case 3:
            work_function = func3;
        }

        pthread_mutex_lock(&thread_pool->thread_pool_mutex);
        task_list[thread_pool->task_list_tail % task_capacity].function = work_function;
        int *arg = (int *)malloc(sizeof(int));
        if (arg == NULL)
        {
            task_list[thread_pool->task_list_tail % task_capacity].function = NULL;
            task_list[thread_pool->task_list_tail % task_capacity].arg = NULL;
            pthread_mutex_unlock(&thread_pool->thread_pool_mutex);
            printf("%s %d: Fail to malloc for arg\n", __func__, __LINE__);
            return FALSE;
        }
        *arg = ++counter;
        task_list[thread_pool->task_list_tail % task_capacity].arg = (void *)arg;
        thread_pool->task_list_tail = (thread_pool->task_list_tail + 1) % thread_pool->task_list_capacity;
        thread_pool->task_list_cur_size++;
        pthread_mutex_unlock(&thread_pool->thread_pool_mutex);
        pthread_cond_signal(&thread_pool->consumer_num);
    }

    return TRUE;
}

void *thread_manager(void *arg)
{
    printf("%s %d: Come here\n", __func__, __LINE__);
    thread_pool_t *thread_pool = NULL;
    worker_thread_t *work_thread = NULL;
    if (arg == NULL)
    {
        printf("%s %d: arg is null\n", __func__, __LINE__);
        pthread_exit(NULL);
    }
    thread_pool = (thread_pool_t *)arg;

    while (1)
    {
        sleep(5);

        pthread_mutex_lock(&thread_pool->thread_busy_mutex);
        printf("%s %d: Have finish %d tasks, fail %d\n",
               __func__, __LINE__, thread_pool->have_finish_work_num, thread_pool->have_fail_work_num);
        pthread_mutex_unlock(&thread_pool->thread_busy_mutex);

        pthread_mutex_lock(&thread_pool->thread_pool_mutex);
        printf("%s %d: Current task num size %d, task capacity %d, min thread num %d, max thread num %d\n",
               __func__, __LINE__, thread_pool->task_list_cur_size, thread_pool->task_list_capacity,
               thread_pool->work_thread_min, thread_pool->work_thread_max);

        if (thread_pool->shutdown == 1)
        {
            pthread_mutex_unlock(&thread_pool->thread_pool_mutex);
            break;
        }

        pthread_mutex_lock(&thread_pool->thread_busy_mutex);
        int thread_busy_num = thread_pool->work_thread_busy;
        pthread_mutex_unlock(&thread_pool->thread_busy_mutex);

        int thread_exit = (thread_pool->work_thread_live - thread_busy_num) / 2;
        /* If current task size is bigger than thread_exit,
         * or equal to thread_exit, do not exit son thread;
         */
        thread_exit = thread_exit - thread_pool->task_list_cur_size;
        if (thread_exit > 0)
        {
            printf("%s %d: Current live thread num %d, busy thread num %d, should exit thread num %d\n", __func__, __LINE__, thread_pool->work_thread_live, thread_busy_num, thread_exit);
            thread_pool->work_thread_exit = thread_exit;
            pthread_mutex_unlock(&thread_pool->thread_pool_mutex);
            pthread_cond_signal(&thread_pool->consumer_num);
            continue;
        }
        int thread_live = thread_pool->work_thread_live;
        int thread_task_size = thread_pool->task_list_cur_size;
        if (thread_task_size == 0)
        {
            pthread_mutex_unlock(&thread_pool->thread_pool_mutex);
            printf("%s %d: Thread task size is zero\n", __func__, __LINE__);
            continue;
        }
        int create_thread_num = thread_task_size - thread_live;
        int add_thread_max_num = thread_pool->work_thread_max - thread_pool->work_thread_live;
        create_thread_num = create_thread_num < add_thread_max_num ? create_thread_num : add_thread_max_num;
        pthread_mutex_unlock(&thread_pool->thread_pool_mutex);

        work_thread_add(thread_pool, create_thread_num);
    }

    int i = 0;
    while (1)
    {
        pthread_mutex_lock(&thread_pool->thread_pool_mutex);
        /*If tid != 0, need to recycle resource.*/
        if (thread_pool->work_thread_live == 0)
        {
            printf("%s %d: Live thread num is zero, start to recycle\n", __func__, __LINE__);
            work_thread = thread_pool->worker_thread;
            for (; i < thread_pool->work_thread_max; i++)
            {
                if (work_thread[i].tid != 0)
                {
                    printf("%s %d: Successfully recycle thread %ld\n",
                           __func__, __LINE__, work_thread[i].tid);
                    pthread_join(work_thread[i].tid, NULL);
                }
            }
            pthread_mutex_unlock(&thread_pool->thread_pool_mutex);
            break;
        }
        else
        {
            /*Wait for a little moment.*/
            printf("%s %d: Live thread num is %d, wait for a moment\n",
                   __func__, __LINE__, thread_pool->work_thread_live);
            pthread_mutex_unlock(&thread_pool->thread_pool_mutex);
            pthread_cond_signal(&thread_pool->consumer_num);
            sleep(1);
        }
    }
    free(thread_pool->task_list);
    free(thread_pool->worker_thread);
    thread_pool->task_list = NULL;
    thread_pool->worker_thread = NULL;
    pthread_exit(NULL);
}

thread_pool_t *thread_pool_create(int min_thread_num, int max_thread_num, int task_list_capacity)
{
    printf("Come into %s\n", __func__);
    thread_pool_t *thread_pool = NULL;

    if (min_thread_num < 0 || max_thread_num < 0 || task_list_capacity < 0 || max_thread_num < min_thread_num)
    {
        printf("Initial parameters error!\n");
        return NULL;
    }
    printf("%s %d: >>>>>> Start creating thread pool <<<<<<\n", __func__, __LINE__);
    do
    {
        thread_pool = (thread_pool_t *)malloc(sizeof(thread_pool_t));
        if (thread_pool == NULL)
        {
            printf("Fail to malloc for thread pool\n");
            break;
        }

        printf("%s %d: >>>>>> Start creating manager thread <<<<<<\n",
               __func__, __LINE__);
        if (pthread_create(&thread_pool->manager_id, NULL, thread_manager, (void *)thread_pool) != 0)
        {
            printf("%s %d: Fail to cretae manager thread\n", __func__, __LINE__);
            break;
        }
        printf("%s %d: >>>>>> Successfully create manager thread <<<<<<\n",
               __func__, __LINE__);

        thread_pool->task_list = NULL;
        thread_pool->worker_thread = NULL;

        printf("%s %d: >>>>>> Start creating task list <<<<<<\n", __func__, __LINE__);
        thread_pool->task_list = (thread_task_t *)malloc(sizeof(thread_task_t) * task_list_capacity);
        if (thread_pool->task_list == NULL)
        {
            printf("Fail to malloc for thread pool task list\n");
            break;
        }
        printf("%s %d: >>>>>> Successfully create task list <<<<<<\n", __func__, __LINE__);
        thread_pool->task_list_front = 0;
        thread_pool->task_list_tail = 0;
        thread_pool->task_list_cur_size = 0;
        thread_pool->task_list_capacity = task_list_capacity;

        printf("%s %d: >>>>>> Start creating work threads list<<<<<<\n",
               __func__, __LINE__);
        thread_pool->worker_thread = (worker_thread_t *)malloc(sizeof(worker_thread_t) * max_thread_num);
        if (thread_pool->worker_thread == NULL)
        {
            printf("Fail to malloc for thread pool work thread list\n");
            break;
        }
        printf("%s %d: >>>>>> Successfully creating work threads list<<<<<<\n",
               __func__, __LINE__);
        thread_pool->work_thread_min = min_thread_num;
        thread_pool->work_thread_max = max_thread_num;
        thread_pool->work_thread_live = 0;
        thread_pool->work_thread_busy = 0;
        thread_pool->have_finish_work_num = 0;
        thread_pool->have_fail_work_num = 0;
        memset(thread_pool->worker_thread, 0, sizeof(worker_thread_t) * max_thread_num);

        printf("%s %d: >>>>>> Start to init conds and mutex locks <<<<<<\n",
               __func__, __LINE__);
        thread_pool->worker_thread = (worker_thread_t *)malloc(sizeof(worker_thread_t) * max_thread_num);
        if (pthread_mutex_init(&thread_pool->thread_pool_mutex, NULL) || pthread_mutex_init(&thread_pool->thread_busy_mutex, NULL) || pthread_cond_init(&thread_pool->producer_num, NULL) || pthread_cond_init(&thread_pool->consumer_num, NULL))
        {
            printf("Initiate lock failed\n");
            break;
        }
        printf("%s %d: >>>>>> Successfully init conds and mutex locks <<<<<<\n",
               __func__, __LINE__);

        thread_pool->shutdown = 0;

        printf("%s %d: >>>>>> Successfully create and initiate thread pool <<<<<<\n",
               __func__, __LINE__);
        return thread_pool;
    } while (0);

    if (thread_pool)
    {
        if (thread_pool->task_list)
        {
            free(thread_pool->task_list);
        }

        if (thread_pool->worker_thread)
        {
            free(thread_pool->worker_thread);
        }

        free(thread_pool);
    }
    printf("%s %d: >>>>>> Fail to create and initiate thread pool <<<<<<\n",
           __func__, __LINE__);
    return NULL;
}

int thread_pool_destroy(thread_pool_t *thread_pool)
{
    printf(">>>>>> Start to destory thread <<<<<<\n");
    if (thread_pool == NULL)
    {
        printf("%s %d: Thread pool is null.\n", __func__, __LINE__);
        return FALSE;
    }
    pthread_mutex_lock(&thread_pool->thread_pool_mutex);
    thread_pool->shutdown = 1;
    pthread_t manager_id = thread_pool->manager_id;
    pthread_mutex_unlock(&thread_pool->thread_pool_mutex);
    pthread_join(manager_id, NULL);
    printf(">>>>>> Successfully to destory thread <<<<<<\n");
    return TRUE;
}

#if 0
typedef enum OPTYPE
{
    CREATE_THREAD_POOL = 1,
    ADD_NEW_TASK,
    DESTORY_THREAD,
    CONTINUE,
    EXIT
} OPTYPE;

int main()
{
    unsigned int opcode;
    int min_thread_num;
    int max_thread_num;
    int task_list_capacity;
    int add_task_num;
    thread_pool_t *thread_pool = NULL;
    while (1)
    {
        if (thread_pool)
        {
            pthread_mutex_lock(&thread_pool->thread_pool_mutex);
            if (thread_pool->task_list_cur_size > 0)
            {
                pthread_mutex_unlock(&thread_pool->thread_pool_mutex);
                sleep(1);
                continue;
            }
        }
        printf("Please input your option:\
               \ninput 1: create a new thread pool and add some task\
               \ninput 2: add new task\
               \ninput 3: destory thread pool\
               \ninput 4: continue\
               \ninput 5: exit\n=================\n");
        scanf("%u", &opcode);

        if (thread_pool)
        {
            if (opcode == CONTINUE)
            {
                printf("Continue\n");
                pthread_mutex_unlock(&thread_pool->thread_pool_mutex);
                sleep(1);
                continue;
            }
        }
        else if (opcode == CONTINUE)
        {
            sleep(1);
            continue;
        }

        switch (opcode)
        {
        case CREATE_THREAD_POOL:
            if (thread_pool)
            {
                printf("%s %d: Error, thread pool is already created.\n", __func__, __LINE__);
                pthread_mutex_unlock(&thread_pool->thread_pool_mutex);
                break;
            }
            counter = 0;
            printf("Create thread pool\n");

            printf("Please input min work thread num: ");
            scanf("%u", &min_thread_num);
            printf("\n");

            printf("Please input max work thread num: ");
            scanf("%u", &max_thread_num);
            printf("\n");

            printf("Please input task list capacity: ");
            scanf("%u", &task_list_capacity);
            printf("\n");

            printf("Please input add task num after creating thread pool: ");
            scanf("%u", &add_task_num);
            printf("\n");

            thread_pool = thread_pool_create(min_thread_num, max_thread_num, task_list_capacity);
            // thread_pool = thread_pool_create(5, 10, 20);
            if (thread_pool == NULL)
            {
                printf("%s %d: Fail to create thread pool\n", __func__, __LINE__);
                break;
            }
            if (thread_task_add(thread_pool, add_task_num))
            {
                printf("%s %d: Fail to add new task\n", __func__, __LINE__);
                break;
            }
            break;
        case ADD_NEW_TASK:
            if (thread_pool == NULL)
            {
                printf("%s %d: Fail to add new task, thread pool is null.\n", __func__, __LINE__);
                break;
            }
            else
            {
                printf("add new task num:\n");
                scanf("%d", &add_task_num);
                printf("\n");
                pthread_mutex_unlock(&thread_pool->thread_pool_mutex);
                if (thread_task_add(thread_pool, add_task_num))
                {
                    printf("%s %d: Fail to add new task\n", __func__, __LINE__);
                    break;
                    ;
                }
            }
            break;
        case DESTORY_THREAD:
            if (thread_pool)
            {
                pthread_mutex_unlock(&thread_pool->thread_pool_mutex);
            }
            if (thread_pool_destroy(thread_pool))
            {
                printf("%s %d: Fail to destory thread pool\n", __func__, __LINE__);
            }
            thread_pool = NULL;
            break;
        case CONTINUE:
            break;
        case EXIT:
            printf("Start to exit...\n");
            if (thread_pool)
            {
                pthread_mutex_unlock(&thread_pool->thread_pool_mutex);
                printf("Thread pool is not null,start to destory thread pool.\n");
            }
            if (thread_pool && thread_pool_destroy(thread_pool))
            {
                printf("%s %d: Fail to destory thread pool\n", __func__, __LINE__);
            }
            printf("Successfully exit.\n");
            return 0;
        default:
            if (thread_pool)
            {
                pthread_mutex_unlock(&thread_pool->thread_pool_mutex);
            }
            printf("Unknow opcode, please input again\n");
            break;
        }
    }
    return 0;
}
#endif
